import louie # message dispatch system, http://louie.berlios.de/
import os
from directions import Directions

class Wall(object):
    def __init__(self, value='clear'):
        self.value = value

    # fire change events when attributes change
    def __setattr__(self, name, value):
        object.__setattr__(self, name, value)
        louie.send('change', self)

class Cell(object):

    def __init__(self, map, pos=(0,0)):
        self.__map = map
        self.pos = pos
        self.value = 'clear'
        self.notes = ''

    def change(self):
        louie.send('change', self, self)

    def getWall(self, dir):
        return self.__map.getWall(self.pos[0], self.pos[1], dir)

    def setWall(self, dir, value):
        self.__map.setWall(self.pos[0], self.pos[1], dir, value)

    def setData(self, other_cell):
        """set the values of this cell (and it's walls) to the values from another cell"""
        self.value = other_cell.value
        self.notes = other_cell.notes
        for dir in Directions: self.setWall(dir, other_cell.getWall(dir))

    # fire change events when attributes change
    def __setattr__(self, name, value):
        object.__setattr__(self, name, value)
        self.change()

    def wallproperty(dir):
        def get(self, type=None): return self.getWall(dir)
        def set(self, value)    : self.setWall(dir, value)
        return property(fget=get, fset=set)

    North = wallproperty(Directions.North)
    East = wallproperty(Directions.East)
    South = wallproperty(Directions.South)
    West = wallproperty(Directions.West)

    del wallproperty

class Map(object):
    """Map of a grid based dungeon"""

    def __iter__(self):
        for c in self.__cells: yield c

    def __setstate__(self, state):
        self.__dict__.update(state)
        self.attachChangeEvents()

    def __init__(self, width = 10, height = 10):
        self.width = width
        self.height = height
        self.__walls = []
        self.__cells = []

        for i in range(2 * width * height + width + height):
            self.__walls.append(Wall('clear'))

        for (x,y) in [(x,y) for y in range(height) for x in range(width)]:
            self.__cells.append(Cell(self, (x,y)))

        self.attachChangeEvents()

    def attachChangeEvents(self):
        """Attach change events on cells, so they handle situations where their walls changes"""
        for cell in self:
            x, y = cell.pos
            for dir in Directions:
                louie.connect(cell.change, 'change', self.__walls[self.calcWallIndex(x, y, dir)])

    def detachChangeEvents(self):
        """Detach all change events from cells"""
        try:
            for cell in self:
                x, y = cell.pos
                for dir in Directions:
                    louie.disconnect(cell.change, 'change', self.__walls[self.calcWallIndex(x, y, dir)])
        except(louie.error.DispatcherKeyError):
            pass

    def bounds_check(self, x, y):
        """Verify that cell x, y is inside the map. Raises IndexError if it isn't"""
        if x < 0 or x >= self.width:
            raise IndexError('x coord %d not inside mapwidth: %d' % (x, self.width))
        if y < 0 or y >= self.height:
            raise IndexError('y coord %d not inside mapheight: %d' % (y, self.height))

    def getCell(self, x, y):
        """Get the Cell object at position x, y"""
        self.bounds_check(x, y)
        return self.__cells[y * self.width + x]

    def calcWallIndex(self, x, y, dir):
        """Calculate the index of a wall for use in the __walls list"""
        self.bounds_check(x, y)
        idx = 2 * y * self.width + y + x
        if dir == Directions.West:  return idx + self.width
        if dir == Directions.East:  return idx + self.width + 1
        if dir == Directions.South: return idx + 2 * self.width + 1
        return idx

    def getWall(self, x, y, dir):
        """Get the value of the wall facing 'dir' from cell at x, y"""
        return self.__walls[self.calcWallIndex(x, y, dir)].value

    def setWall(self, x, y, dir, value):
        """Set the value of the wall facing 'dir' from cell at x, y"""
        self.__walls[self.calcWallIndex(x, y, dir)].value = value

    def expandWidth (self):
        """Expand the map width of the map with one cell per row"""
        x = self.width
        self.width += 1

        for y in range(self.height):
            self.__cells.insert(y * self.width + x, Cell(self, (x, y)))
            self.__walls.insert(self.calcWallIndex(x, y, Directions.North), Wall('clear'))
            self.__walls.insert(self.calcWallIndex(x, y, Directions.East), Wall('clear'))

        self.__walls.insert(self.calcWallIndex(x, self.height - 1, Directions.South), Wall('clear'))

        self.detachChangeEvents()
        self.attachChangeEvents()

    def expandHeight(self):
        y = self.height
        self.height += 1

        for x in range(self.width):
            self.__cells.insert(y * self.width + x, Cell(self, (x, y)))
            self.__walls.insert(self.calcWallIndex(x, y, Directions.West), Wall('clear'))
            self.__walls.insert(self.calcWallIndex(x, y, Directions.South), Wall('clear'))

        self.__walls.insert(self.calcWallIndex(self.width - 1, y, Directions.East), Wall('clear'))

        self.detachChangeEvents()
        self.attachChangeEvents()

    def moveMap(self, dir):
        self.detachChangeEvents()

        rects = {
            Directions.North: {
                'region': (0, 1, self.width, self.height - 1), 'new_pos':(0,0),
                'empty':(self.width, 1), 'empty_pos':(0, self.height - 1)
            },
            Directions.South: {
                'region': (0, 0, self.width, self.height - 1), 'new_pos':(0,1),
                'empty':(self.width, 1), 'empty_pos':(0, 0)
            },
            Directions.East: {
                'region': (0, 0, self.width - 1, self.height), 'new_pos':(1,0),
                'empty':(1, self.height), 'empty_pos':(0, 0)
            },
            Directions.West: {
                'region': (1, 0, self.width - 1, self.height), 'new_pos':(0,0),
                'empty':(1, self.height), 'empty_pos':(self.width - 1, 0)
            },
        }
        region = self.getMapregion(*rects[dir]['region'])
        empty_region = Map(*rects[dir]['empty'])
        self.setMapregion(empty_region, *rects[dir]['empty_pos'])
        self.setMapregion(region, *rects[dir]['new_pos'])

        self.attachChangeEvents()

    def getMapregion(self, x, y, w, h):
        """return a new map initialised with the data from a region of this map"""
        new_map = Map(w, h)
        for (dx,dy) in [(dx,dy) for dy in range(h) for dx in range(w)]:
            new_map.getCell(dx, dy).setData(self.getCell(dx + x, dy + y))
        return new_map

    def setMapregion(self, other, x, y):
        """set map (or portion of it) to data from another map."""
        for (dx,dy) in [(dx,dy) for dy in range(other.height) for dx in range(other.width)]:
            self.getCell(x + dx, y + dy).setData(other.getCell(dx, dy))


if __name__ == '__main__':
    pass




